﻿//*************************************************************************************************
// WebServiceResult.cs
// Owner: Mahipal Kante
//
// UI of the Web Test Result Tab for Web Service request/response
//
// Copyright(c) Microsoft Corporation, 2010
//*************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualStudio.TestTools.WebTesting;
using EnvDTE;
using mshtml;
using System.IO;
using System.Xml;
using Microsoft.VisualStudio.TestTools.WebTesting.Rules;
using WebTest.WebService.Plugin.Runtime;
using System.Xml.XPath;

namespace WebTest.WebService.Plugin.ResultTab
{
    internal partial class WebServiceResult : UserControl
    {
        public WebServiceResult(Connect connect)
        {
            this.connect = connect;

            InitializeComponent();

            this.xmlViewRequest = new XmlTreeView(this.treeViewRequest);
            this.xmlViewResponse = new XmlTreeView(this.treeViewResponse);
            this.requestTextBox.TextChanged += new EventHandler(requestTextBox_TextChanged);
            this.editRequest.CheckedChanged += new EventHandler(EditRequestCheckedChanged);
            this.treeViewResponse.AfterSelect += new TreeViewEventHandler(treeViewResponse_AfterSelect);

            this.ResponseTab.SelectedTab = this.ResponseTabPageTreeView;
            this.RequestTab.SelectedTab = this.RequestTabPageTreeView;
            AddContextMenus();
            AddToolTips();
        }

        internal WebTestDetail WebTestDetail
        {
            get;
            set;
        }

        internal void SetWebTestRequestResult(WebTestRequestResult webTestRequestResult, WebTestResultUnit webTestResultUnit)
        {
            if (this.xmlViewRequest.MessageEdited)
            {
                DialogResult result = MessageBox.Show("You have updated the Request Content. Do you want to save?", "Request Update Pending", MessageBoxButtons.YesNo);

                if (result.Equals(DialogResult.Yes))
                {
                    UpdateWebTestForRequest();
                }
            }

            ResetUI();

            this.webTestRequestResult = webTestRequestResult;
            this.webTestResultUnit = webTestResultUnit;

            ShowRequest(webTestRequestResult.Request);
            ShowResponse(webTestRequestResult.Response);
        }

        private void AddContextMenus()
        {
            this.responseEditContextMenu = new ContextMenu();
            this.requestEditContextMenu = new ContextMenu();
            this.responseViewContextMenu = new ContextMenu();
            this.requestViewContextMenu = new ContextMenu();

            this.responseEditContextMenu.MenuItems.Add(new MenuItem("Add Extraction Rule", AddExtractionRule));
            this.responseEditContextMenu.MenuItems.Add(new MenuItem("Add Validation Rule", AddValidationRule));

            MenuItem m = new MenuItem("Parameterize");
            m.MenuItems.Add(new MenuItem("Type Parameter", AddParameter));
            m.Popup += new EventHandler(ParameterMenuClicked);
            this.requestEditContextMenu.MenuItems.Add(m);
        }

        private void AddToolTips()
        {
            // Set Tooltips
            ToolTip toolTip = new ToolTip();
            toolTip.SetToolTip(this.editRequest, "Edit the original content of request");

            toolTip = new ToolTip();
            toolTip.SetToolTip(this.requestXMLEditor, "View request contents in XML Editor");

            toolTip = new ToolTip();
            toolTip.SetToolTip(this.responseXMLEditor, "View response contents in XML Editor");

            toolTip = new ToolTip();
            toolTip.SetToolTip(this.updateLinkLabel, "Update the content of original request");
        }

        private void EditRequestCheckedChanged(object sender, EventArgs e)
        {
            bool editing = false;
            if (this.webTestResultUnit != null)
            {
                if (this.editRequest.Checked)
                {
                    if (this.WebTestDetail != null)
                    {
                        WebTestRequest requestInWebTest = 
                            WebTestDetail.WebTest.GetItem(this.webTestResultUnit.DeclarativeWebTestItemId) 
                            as WebTestRequest;

                        if (requestInWebTest != null)
                        {
                            ShowRequest(requestInWebTest);
                            editing = true;
                        }
                    }
                }
                else
                {
                    ShowRequest(this.webTestRequestResult.Request);
                }
            }

            if (editing)
            {
                this.requestTextBox.ReadOnly = false;
                this.updateLinkLabel.Visible = true;
                this.treeViewRequest.LabelEdit = true;
                this.treeViewRequest.ContextMenu = this.requestEditContextMenu;
            }
            else
            {
                this.requestTextBox.ReadOnly = true;
                this.updateLinkLabel.Visible = false;
                this.treeViewRequest.LabelEdit = false;
                this.treeViewRequest.ContextMenu = this.requestViewContextMenu;
            }
        }

        private void ResetUI()
        {
            this.webTestRequestResult = null;
            this.webTestResultUnit = null;

            this.xPathNavigator = null;
            this.xPathNamespaces = null;

            this.requestTextBox.Text = string.Empty;
            this.responseTextBox.Text = string.Empty;
            this.XPathTextBox.Text = string.Empty;
            this.responseContentHandler = null;
            this.requestContentHandler = null;
            this.treeViewRequest.Nodes.Clear();
            this.treeViewResponse.Nodes.Clear();
            this.editRequest.Checked = false;
            this.updateLinkLabel.Visible = false;

            this.requestTreePopulated = false;
            this.xmlViewRequest.MessageEdited = false;
        }

        private void ShowResponse(WebTestResponse webTestResponse)
        {
            if (webTestResponse != null && webTestResponse.BodyBytes != null)
            {
                responseContentHandler = ContentHandlerFactory.GetHandler(webTestResponse.ContentType);
                if (responseContentHandler != null)
                {
                    if (responseContentHandler.IsBinary)
                    {
                        responseContentHandler.MessageBytes = webTestResponse.BodyBytes;
                    }
                    else
                    {
                        responseContentHandler.MessageString = webTestResponse.BodyString;
                    }

                    this.responseTextBox.Text = responseContentHandler.XmlString;
                    this.xmlViewResponse.ShowXML(responseContentHandler.XmlDocument);
                }
            }

            if (this.treeViewResponse.Nodes.Count > 0)
            {
                if (webTestResultUnit == null || WebTestDetail == null)
                {
                    this.treeViewResponse.ContextMenu = this.responseViewContextMenu;
                }
                else
                {
                    this.treeViewResponse.ContextMenu = this.responseEditContextMenu;
                }
            }
            else
            {
                this.treeViewResponse.ContextMenu = null;
            }

            if (string.IsNullOrEmpty(this.responseTextBox.Text))
            {
                this.responseXMLEditor.Visible = false;
            }
            else
            {
                this.responseXMLEditor.Visible = true;
            }
        }

        private void ShowRequest(WebTestRequest webTestRequest)
        {
            this.requestContentHandler = null;
            if (webTestRequest != null && webTestRequest.Body != null)
            {
                BinaryHttpBody binaryHttpBody = webTestRequest.Body as BinaryHttpBody;
                if (binaryHttpBody != null)
                {
                    this.requestContentHandler = ContentHandlerFactory.GetHandler(binaryHttpBody.ContentType);
                    if (this.requestContentHandler != null)
                    {
                        this.requestContentHandler.MessageBytes = binaryHttpBody.Data;
                    }
                }

                StringHttpBody stringHttpBody = webTestRequest.Body as StringHttpBody;
                if (stringHttpBody != null)
                {
                    this.requestContentHandler = ContentHandlerFactory.GetHandler(stringHttpBody.ContentType);
                    if (this.requestContentHandler != null)
                    {
                        this.requestContentHandler.MessageString = stringHttpBody.BodyString;
                    }
                }

                if (this.requestContentHandler != null)
                {
                    this.requestTextBox.Text = this.requestContentHandler.XmlString;
                    xmlViewRequest.ShowXML(this.requestContentHandler.XmlDocument);
                    this.requestTreePopulated = true;
                }
            }

            this.xmlViewRequest.MessageEdited = false;

            if (string.IsNullOrEmpty(this.requestTextBox.Text))
            {
                this.requestXMLEditor.Visible = false;
                this.editRequest.Visible = false;
            }
            else
            {
                this.requestXMLEditor.Visible = true;
                this.editRequest.Visible = (this.WebTestDetail != null);
            }
        }

        private void ParameterMenuClicked(object sender, EventArgs e)
        {
            MenuItem menuItem = sender as MenuItem;

            if (menuItem != null)
            {
                DeclarativeWebTest webTest = WebTestDetail.WebTest;
                menuItem.MenuItems.Clear();

                menuItem.MenuItems.Add(new MenuItem("Type Parameter", AddParameter));
                for (int i = 1; i < this.webTestResultUnit.DeclarativeWebTestItemId; i++)
                {
                    WebTestRequest requestInWebTest = webTest.GetItem(i) as WebTestRequest;
                    if (requestInWebTest != null)
                    {
                        foreach (ExtractionRuleReference reference in requestInWebTest.ExtractionRuleReferences)
                        {
                            menuItem.MenuItems.Add(new MenuItem(reference.ContextParameterName, AddParameter));
                        }
                    }
                }
            }
        }

        private void AddParameter(object sender, EventArgs e)
        {
            MenuItem menuItem = sender as MenuItem;
            if (menuItem == null)
            {
                return;
            }
            this.xmlViewRequest.AddParameter(menuItem.Text);
        }

        private void requestTextBox_TextChanged(object sender, EventArgs e)
        {
            this.xmlViewRequest.MessageEdited = true;
        }

        /// <summary>
        /// 1. Enable Extraction Rule if known Web Test
        /// 2. Before adding extraction rule, check if there is an open unsaved document
        /// 3. Prompt user to save if unsaved, and save it
        /// 4. Parse Declarative Web Test from file, and add extraction rule
        /// 5. Open the document again, if it was open previously
        /// 6. Using TimeStamp,determine if Declarative Web Test at hand is uptodate.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AddExtractionRule(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(this.XPathTextBox.Text))
            {
                return;
            }

            if (WebTestDetail != null)
            {
                // If there are pending changes, save first, as we are going to make our 
                // changes to the file directly.
                WebTestDetail.PrepareToSave();

                WebTestRequest requestInWebTest = WebTestDetail.WebTest.GetItem(this.webTestResultUnit.DeclarativeWebTestItemId) as WebTestRequest;

                if (requestInWebTest != null)
                {
                    string contextParamName = "xpathParam";
                    TreeNode selectedNode = this.treeViewResponse.SelectedNode;
                    if (selectedNode != null)
                    {
                        if (selectedNode.Nodes.Count > 0)
                        {
                            contextParamName = selectedNode.Text;
                        }
                        else if (selectedNode.Parent != null)
                        {
                            contextParamName = selectedNode.Parent.Text;
                        }
                    }

                    ExtractionRuleReference extractTextReference = new ExtractionRuleReference();
                    extractTextReference.Type = typeof(XPathExtractionRule);
                    extractTextReference.ContextParameterName = contextParamName;
                    extractTextReference.Properties.Add(new PluginOrRuleProperty("XPathToSearch", this.XPathTextBox.Text));
                    extractTextReference.Properties.Add(new PluginOrRuleProperty("Index", "0"));
                    extractTextReference.Properties.Add(new PluginOrRuleProperty("ExtractRandomMatch", "False"));

                    requestInWebTest.ExtractionRuleReferences.Add(extractTextReference);

                    WebTestDetail.Save();

                    string message = string.Format(
@"The extraction rule was created, and the text is extracted to a context parameter named '{0}'. You will need to add bindings to this context parameter in the appropriate places in the script.

XPath to Evaluate: ‘{1}’

The extraction rule was created under the following request:
‘{2}’", contextParamName, this.XPathTextBox.Text, requestInWebTest.Url);

                    MessageBox.Show(message, "Extraction Rule Created");
                }
            }
        }

        private void AddValidationRule(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(this.XPathTextBox.Text))
            {
                return;
            }

            if (WebTestDetail != null)
            {
                TreeNode treeNode = this.treeViewResponse.SelectedNode;
                TreeNode parentNode = treeNode.Parent;

                if (parentNode == null)
                {
                    return;
                }

                // If there are pending changes, save first, as we are going to make our 
                // changes to the file directly.
                WebTestDetail.PrepareToSave();

                WebTestRequest requestInWebTest = WebTestDetail.WebTest.GetItem(this.webTestResultUnit.DeclarativeWebTestItemId) as WebTestRequest;

                if (requestInWebTest != null)
                {
                    string expectedValue = string.Empty;
                    SetupResponseXpath();

                    // Compile a standard XPath expression
                    XPathExpression expr;
                    expr = this.xPathNavigator.Compile(this.XPathTextBox.Text);
                    expr.SetContext(xPathNamespaces);
                    XPathNodeIterator iterator = this.xPathNavigator.Select(expr);

                    if (iterator.MoveNext())
                    {
                        XPathNavigator nav2 = iterator.Current.Clone();
                        expectedValue = nav2.InnerXml;
                    }

                    ValidationRuleReference validationTextReference = new ValidationRuleReference();
                    validationTextReference.Type = typeof(XPathValidationRule);
                    validationTextReference.Properties.Add(new PluginOrRuleProperty("XPathToSearch", this.XPathTextBox.Text));
                    validationTextReference.Properties.Add(new PluginOrRuleProperty("Index", "0"));
                    validationTextReference.Properties.Add(new PluginOrRuleProperty("IgnoreCase", "True"));
                    validationTextReference.Properties.Add(new PluginOrRuleProperty("ExpectedValue", expectedValue));

                    requestInWebTest.ValidationRuleReferences.Add(validationTextReference);

                    WebTestDetail.Save();

                    string message = string.Format(
@"The validation rule was created.

XPath to Evaluate: ‘{0}’
Expected Value: '{1}'

The extraction rule was created under the following request:
‘{2}’", this.XPathTextBox.Text, expectedValue, requestInWebTest.Url);

                    MessageBox.Show(message, "Validation Rule Created");
                }
            }
        }

        private void ResponseTabChanged(object sender, EventArgs e)
        {
        }

        private void RequestTabChanged(object sender, EventArgs e)
        {
            if (requestTreePopulated && (!this.editRequest.Checked))
            {
                return;
            }

            if (string.IsNullOrEmpty(this.requestTextBox.Text))
            {
                return;
            }

            if (this.RequestTab.SelectedTab.Equals(this.RequestTabPageTreeView))
            {
                if (this.requestContentHandler != null)
                {
                    this.requestContentHandler.XmlString = this.requestTextBox.Text;
                    this.xmlViewRequest.ShowXML(this.requestContentHandler.XmlDocument);
                }
            }
            else
            {
                if (this.requestContentHandler != null)
                {
                    this.requestTextBox.Text = this.requestContentHandler.XmlString;
                }
            }
            this.requestTreePopulated = true;
        }

        private void requestXMLEditor_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            if (this.requestContentHandler != null)
            {
                string message = string.Empty;
                if (!this.RequestTab.SelectedTab.Equals(this.RequestTabPageTreeView))
                {
                    this.requestContentHandler.XmlString = this.requestTextBox.Text;
                }

                this.connect.ShowInXMLEditor(this.webTestRequestResult.Request.Url + " [Request]",
                    this.requestContentHandler.XmlString);
            }
        }

        private void responseXMLEditor_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            if (this.responseContentHandler != null)
            {
                this.connect.ShowInXMLEditor(this.webTestRequestResult.Request.Url + " [Response]", this.responseTextBox.Text);
            }
        }

        private void updateLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            UpdateWebTestForRequest();
        }

        private void UpdateWebTestForRequest()
        {
            if (WebTestDetail != null && this.requestContentHandler != null)
            {
                // If there are pending changes, save first, as we are going to make our 
                // changes to the file directly.
                WebTestDetail.PrepareToSave();

                // Get the original request
                WebTestRequest requestInWebTest =
                    WebTestDetail.WebTest.GetItem(this.webTestResultUnit.DeclarativeWebTestItemId)
                    as WebTestRequest;
                if (requestInWebTest == null)
                {
                    // error
                    return;
                }

                // Update content if edited in text box
                if (!this.RequestTab.SelectedTab.Equals(this.RequestTabPageTreeView))
                {
                    this.requestContentHandler.XmlString = this.requestTextBox.Text;
                }

                // Fill the request body with updated content
                if (this.requestContentHandler.IsBinary)
                {
                    BinaryHttpBody binaryHttpBody = requestInWebTest.Body as BinaryHttpBody;
                    if (binaryHttpBody != null)
                    {
                        // set the new binary data into http body
                        binaryHttpBody.Data = this.requestContentHandler.MessageBytes;
                        return;
                    }
                }
                else
                {
                    StringHttpBody stringHttpBody = requestInWebTest.Body as StringHttpBody;
                    if (stringHttpBody != null)
                    {
                        stringHttpBody.BodyString = this.requestContentHandler.MessageString;
                    }
                }

                // Save WebTest
                WebTestDetail.Save();
                this.xmlViewRequest.MessageEdited = false;
            }
        }

        #region XPathResponse
        private void treeViewResponse_AfterSelect(object sender, TreeViewEventArgs e)
        {
            TreeNode selectedNode = this.treeViewResponse.SelectedNode;
            if (selectedNode == null)
            {
                return;
            }


            TreeNode parentNode = selectedNode.Parent;
            if (parentNode == null)
            {
                this.XPathTextBox.Text = "/" + GetXPathSubExpression(selectedNode.Text);
                return;
            }

            if (selectedNode.Nodes.Count == 0)
            {
                selectedNode = parentNode;
                parentNode = parentNode.Parent;
            }

            string xPath = string.Empty;

            bool addedDoubleSlashes = false;
            while (parentNode != null)
            {
                int index = 1;
                if (parentNode.Nodes.Count > 1)
                {
                    for (int i = 0; i < selectedNode.Index; i++)
                    {
                        if (parentNode.Nodes[i].Text.Equals(selectedNode.Text))
                        {
                            index++;
                        }
                    }

                    string xpathExpression = GetXPathSubExpression(selectedNode.Text);

                    if (index > 1)
                    {
                        xPath = "/" + xpathExpression + "[" + index + "]" + xPath;
                    }
                    else
                    {
                        xPath = "/" + xpathExpression + xPath;
                    }

                    addedDoubleSlashes = false;
                }
                else
                {
                    if (string.IsNullOrEmpty(xPath))
                    {
                        xPath = "/" + GetXPathSubExpression(selectedNode.Text);
                    }
                    else if (!addedDoubleSlashes)
                    {
                        // Ignore intermediate single elements to reduce XPath length
                        xPath = "/" + xPath;
                        addedDoubleSlashes = true;
                    }
                }

                selectedNode = parentNode;
                parentNode = parentNode.Parent;
            }

            if (!addedDoubleSlashes)
            {
                xPath = "/" + xPath;
            }
            this.XPathTextBox.Text = xPath;
        }

        private string GetXPathSubExpression(string name)
        {
            if (name.Contains(':'))
            {
                return name;
            }
            else
            {
                return string.Format("*[local-name()='{0}']", name);
            }
        }

        private void XPathButton_Click(object sender, EventArgs e)
        {
            try
            {
                SetupResponseXpath();

                // Compile a standard XPath expression
                XPathExpression expr;
                expr = this.xPathNavigator.Compile(this.XPathTextBox.Text);
                expr.SetContext(xPathNamespaces);
                XPathNodeIterator iterator = this.xPathNavigator.Select(expr);

                int i = 1;
                while (iterator.MoveNext())
                {
                    XPathNavigator nav2 = iterator.Current.Clone();
                    string value = string.Empty;
                    if (nav2.NodeType.Equals(XPathNodeType.Element))
                    {
                        value = nav2.InnerXml;
                    }
                    else
                    {
                        value = nav2.Value;
                    }

                    if (i < iterator.Count)
                    {
                        string displayText = string.Format("Result[{0} of {1}]:{2}{3}{4}{5}{6}Do you want to display next?",
                            i,
                            iterator.Count,
                            Environment.NewLine,
                            Environment.NewLine,
                            value,
                            Environment.NewLine,
                            Environment.NewLine);
                        DialogResult result = MessageBox.Show(displayText, "XPath Result", MessageBoxButtons.YesNoCancel);
                        if (!result.Equals(DialogResult.Yes))
                        {
                            return;
                        }
                    }
                    else
                    {
                        string displayText = string.Format("Result[{0} of {1}]:{2}{3}{4}",
                            i,
                            iterator.Count,
                            Environment.NewLine,
                            Environment.NewLine,
                            value);
                        MessageBox.Show(displayText, "XPath Result");

                        return;
                    }
                    i++;
                }

                MessageBox.Show("No Results to Display", "XPath Result");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "XPath Result");
            }
        }

        private void SetupResponseXpath()
        {
            if (xPathNavigator == null && this.responseContentHandler != null)
            {

                this.xPathNavigator = new XPathDocument(
                    new StringReader(this.responseContentHandler.XmlString)
                    ).CreateNavigator();

                this.xPathNamespaces = new XmlNamespaceManager(this.xPathNavigator.NameTable);
                while (this.xPathNavigator.MoveToFollowing(XPathNodeType.Element))
                {
                    IDictionary<string, string> whatever = this.xPathNavigator.GetNamespacesInScope(XmlNamespaceScope.All);
                    foreach (KeyValuePair<string, string> key in whatever)
                    {
                        this.xPathNamespaces.AddNamespace(key.Key, key.Value);
                    }
                }
            }
        }

        #endregion

        private WebTestRequestResult webTestRequestResult;
        private WebTestResultUnit webTestResultUnit;
        private ContextMenu responseEditContextMenu;
        private ContextMenu requestEditContextMenu;

        private XmlTreeView xmlViewRequest;
        private XmlTreeView xmlViewResponse;

        private ContextMenu responseViewContextMenu;
        private ContextMenu requestViewContextMenu;

        private bool requestTreePopulated = false;

        private XPathNavigator xPathNavigator = null;
        private XmlNamespaceManager xPathNamespaces = null;

        private ContentHandler responseContentHandler;
        private ContentHandler requestContentHandler;
        private Connect connect;
    }
}
